/*
 * Copyright (C) 2018 OTA keys S.A.
 *
 * This file is subject to the terms and conditions of the GNU Lesser
 * General Public License v2.1. See the file LICENSE in the top level
 * directory for more details.
 */

/**
 * @ingroup tests
 * @{
 *
 * @file
 * @brief       Benchmark application for file systems
 *
 * @author      Vincent Dupont <vincent@otakeys.com>
 * @}
 */

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>

#include "hwboard.h"

#if defined(MODULE_SPIFFS)
#include "fs/spiffs_fs.h"
static char fs_name[] = "spiffs";

static struct spiffs_desc spiffs_desc = {
    .lock = MUTEX_INIT,
};

static vfs_mount_t _bench_mount = {
    .fs = &spiffs_file_system,
    .mount_point = "/bench",
    .private_data = &spiffs_desc,
};

static void init_fs(void)
{
#if SPIFFS_HAL_CALLBACK_EXTRA == 1
    spiffs_desc.dev = _dev;
#endif
    mtd_init(_dev);
}
#elif defined(MODULE_LITTLEFS)
#include "fs/littlefs_fs.h"
static char fs_name[] = "littlefs";

static littlefs_desc_t littlefs_desc = {
    .lock = MUTEX_INIT,
};

static vfs_mount_t _bench_mount = {
    .fs = &littlefs_file_system,
    .mount_point = "/bench",
    .private_data = &littlefs_desc,
};

static void init_fs(void)
{
    littlefs_desc.dev = _dev;
    mtd_init(_dev);
}
#else
static char fs_name[] = "raw_mtd";

static void init_fs(void)
{
    // mtd_init(_dev);
}

#define BASE_NAME "test"
#define DO_NOT_MOUNT
#endif

#ifndef FILE_LOOP_SIZE
#define FILE_LOOP_SIZE (10u)
#endif

#ifndef LOOP_SIZE
#define LOOP_SIZE (1u)
#endif

#ifndef BASE_NAME
#define BASE_NAME "/bench/test"
#endif

#ifndef BUF_SIZE
#define BUF_SIZE (256)
#endif

static char buf[BUF_SIZE];

int test_bench(void)
{
    printf("benchmarking file system: %s\n", fs_name);
    init_fs();

    for (size_t i = 0; i < BUF_SIZE; i++)
    {
        buf[i] = '0' + i % 10;
    }

    uint32_t begin;
    uint32_t end;
    uint32_t diff;

#ifndef DO_NOT_MOUNT
    puts("[BEGIN] Format test...");
    begin = getCurrentMicros();
    vfs_format(&_bench_mount);
    end = getCurrentMicros();
    diff = end - begin;
    printf("Test time: %" uint32_t "us\n", (diff));
    puts("[END] Format test");

    puts("[BEGIN] Mount test...");
    begin = getCurrentMicros();
    vfs_mount(&_bench_mount);
    end = getCurrentMicros();
    diff = end - begin;
    printf("Test time: %" uint32_t "us\n", (diff));
    puts("[END] Mount test");
#endif

    int fd;
    uint32_t total_time = 0;
    int ret;

    puts("[BEGIN] Write test...");
    for (unsigned f = 0; f < FILE_LOOP_SIZE; f++)
    {
        char name[20];
        sprintf(name, BASE_NAME "%d", f);
        begin = getCurrentMicros();

        FlashFile fd;
        fd.open_write(name);
        for (unsigned i = 0; i < LOOP_SIZE; i++)
        {
            ret = fd.write(buf, sizeof(buf));
            if (ret != (int)sizeof(buf))
            {
                printf("[END] error when writing (%d)\n", ret);
                break;
            }
        }
        fd.close();
        end = getCurrentMicros();
        diff = end - begin;
        printf("File #%d, %u bytes written in: %d"
               "us\n",
               f,
               LOOP_SIZE * sizeof(buf), (diff));
        total_time += (diff);
    }
    printf("Mean time: %d"
           "us\n",
           total_time / (uint32_t)FILE_LOOP_SIZE);
    uint32_t t = ((FILE_LOOP_SIZE * LOOP_SIZE * sizeof(buf) * 1000.0) / total_time);
    printf("Throughput: %d kB/s\n", t);
    puts("[END] Write test");

    total_time = 0;
    uint32_t read_total = 0;
    puts("[BEGIN] Read test...");
    for (unsigned f = 0; f < FILE_LOOP_SIZE; f++)
    {
        char name[20];
        sprintf(name, BASE_NAME "%d", f);
        begin = getCurrentMicros();
        FlashFile fd;
        fd.open_rdonly(name);
        int total = 0;
        int ret;
        while ((ret = fd.read(buf, sizeof(buf))) > 0)
        {
            total += ret;
        }
        fd.close();
        end = getCurrentMicros();
        diff = end - begin;
        printf("File #%d, %d bytes read in: %d"
               "us\n",
               f,
               total, (diff));
        total_time += (diff);
        read_total += total;
    }
    printf("Mean time: %d"
           "us\n",
           total_time / (uint32_t)FILE_LOOP_SIZE);
    uint32_t tt = ((read_total * 1000.0) / total_time);
    printf("Throughput: %d kB/s\n", tt);
    puts("[END] Read test");

    total_time = 0;
    puts("[BEGIN] Remove test...");
    for (unsigned f = 0; f < FILE_LOOP_SIZE; f++)
    {
        char name[20];
        sprintf(name, BASE_NAME "%d", f);
        begin = getCurrentMicros();
        flash_fs.unlink(name);
        end = getCurrentMicros();
        diff = end - begin;
        printf("File #%d, test time: %d"
               "us\n",
               f, (diff));
        total_time += diff;
    }
    printf("Mean time: %d"
           "us\n",
           total_time / (uint32_t)FILE_LOOP_SIZE);
    puts("[END] Remove test");

#ifndef DO_NOT_MOUNT
    puts("[BEGIN] Unmount test...");
    begin = getCurrentMicros();
    vfs_umount(&_bench_mount);
    end = getCurrentMicros();
    diff = end - begin;
    printf("Test time: %" uint32_t "us\n", (diff));
    puts("[END] Unmount test");
#endif

    return 0;
}
